package lab.cadl.lirui.markdown.core.model

import cn.hutool.core.convert.Convert
import lab.cadl.lirui.markdown.core.exceptions.MarkdownException
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties

interface RefRegistry {
    fun resolveRef(refId: String): RefTarget
    fun addTarget(refTarget: RefTarget): Boolean
}

class RefRegistryImpl : RefRegistry {
    private val registry: MutableMap<String, RefTarget> = mutableMapOf()

    override fun resolveRef(refId: String): RefTarget {
        return registry[refId] ?: throw MarkdownException("refId $refId not in registry")
    }

    override fun addTarget(refTarget: RefTarget): Boolean {
        return if (refTarget.refId.isBlank() || refTarget.refId in registry) {
            false
        } else {
            registry[refTarget.refId] = refTarget
            true
        }
    }
}

interface VariableRegistry {
    fun hasVariable(name: String): Boolean
    fun resolveVariable(name: String): String
    fun updateVariable(name: String, value: String)
}

class VariableRegistryImpl(private val parent: VariableRegistry?) : VariableRegistry {
    private val table: MutableMap<String, String> = mutableMapOf()

    override fun resolveVariable(name: String): String {
        if (table.containsKey(name)) {
            return table[name] ?: throw MarkdownException("variable '${name}' not found")
        }

        if (parent != null) {
            return parent.resolveVariable(name)
        }

        throw MarkdownException("variable '${name}' not found")
    }

    override fun updateVariable(name: String, value: String) {
        table[name] = value
    }

    override fun hasVariable(name: String): Boolean {
        return if (table.containsKey(name)) {
            true
        } else {
            parent?.hasVariable(name) ?: false
        }
    }
}

interface Element {
    var parent: Element?
    val children: List<Element>

    fun accept(visitor: Visitor)

    fun prevChild(child: Element): Element?
    fun nextChild(child: Element): Element?

    fun appendChild(child: Element): Element
    fun removeChild(child: Element): Element

    fun contentString(): String

    fun debugDisplay(): String
    fun debugDisplay(indentLevel: Int): String

    fun prop(name: String): String
    fun prop(name: String, value: String)
    fun hasProp(name: String): Boolean
}

interface RefTarget {
    val refId: String
    val refLabel: String
    val title: String
    val fixSeq: String?
}

fun <T, R> KProperty1<T, R>.getUnsafe(receiver: Any): R {
    return get(receiver as T)
}

interface TextContent {
    val content: String
}


@Target(AnnotationTarget.PROPERTY)
@Retention
annotation class NonDisplayProperty

abstract class AbstractElement(@NonDisplayProperty override var parent: Element?) : Element {
    @NonDisplayProperty
    final override val children: MutableList<Element> = mutableListOf()

    private val properties: MutableMap<String, String> = mutableMapOf()

    @NonDisplayProperty
    val documentCounter: Int

    @NonDisplayProperty
    val sectionCounter: Int

    init {
        if (this is Section) {
            sectionCounter = 0
            documentCounter = 0
        } else {
            sectionCounter = rootSection?.genCount(javaClass) ?: 1
            documentCounter = document?.genCount(javaClass) ?: 1
        }
    }

    override fun toString(): String {
        val name = javaClass.simpleName
        return "$name { ${attributesString()} }"
    }

    open fun attributesString(): String {
        val props = this::class.memberProperties.filter { prop ->
            prop.annotations.none { a ->
                a is NonDisplayProperty
            }
        }
        return props.joinToString(", ") { it.name + " = " + compactLongString(it.getUnsafe(this)) }
    }

    override fun debugDisplay(indentLevel: Int): String {
        val indent = "  ".repeat(indentLevel)
        val entries = mutableListOf(indent + this.toString())
        entries.addAll(children.map { it.debugDisplay(indentLevel + 1) })
        return entries.joinToString("\n")
    }

    override fun debugDisplay(): String {
        return debugDisplay(0)
    }

    override fun prevChild(child: Element): Element? {
        val index = children.indexOf(child)
        return when {
            index < 0 -> throw MarkdownException("$child is not child of ${this}")
            index == 0 -> null
            else -> children[index - 1]
        }
    }

    override fun nextChild(child: Element): Element? {
        val index = children.indexOf(child)
        val last = children.size - 1
        return when {
            index < 0 -> throw MarkdownException("$child is not child of ${this}")
            index == last -> null
            else -> children[index + 1]
        }
    }

    override fun appendChild(child: Element): Element {
        if (child !in children) {
            children.add(child)

            if (child.parent !== this) {
                if (child.parent != null) {
                    child.parent!!.removeChild(child)
                }

                child.parent = this
            }
        }

        return this
    }

    override fun removeChild(child: Element): Element {
        children.remove(child)
        return this
    }

    @NonDisplayProperty
    val section: Section?
        get() {
            var parentElement = parent
            while (parentElement != null) {
                if (parentElement is Section) {
                    return parentElement
                }

                parentElement = parentElement.parent
            }

            return null
        }

    @NonDisplayProperty
    val rootSection: Section?
        get() {
            var section = this.section
            while (section != null && section.parent !is MarkdownDocument) {
                section = section.parentSection
            }

            return section
        }

    @NonDisplayProperty
    val document: MarkdownDocument?
        get() {
            var element: AbstractElement? = this
            while (element != null) {
                if (element is MarkdownDocument) {
                    return element
                }
                element = element.parent as AbstractElement?
            }

            return null
        }

    override fun contentString(): String {
        return if (children.isEmpty()) {
            ""
        } else {
            children.joinToString("") { it.contentString() }
        }
    }

    override fun prop(name: String): String {
        return properties.getOrDefault(name, "")
    }

    override fun prop(name: String, value: String) {
        properties[name] = value
    }

    override fun hasProp(name: String): Boolean {
        return properties.containsKey(name)
    }

    companion object {
        private fun <T> compactLongString(o: T): String {
            return if (o is String && (o.length > 50 || o.contains('\n'))) {
                "<TEXT#${o.length}>"
            } else {
                o.toString()
            }
        }
    }
}

abstract class Inline(parent: AbstractElement?) : AbstractElement(parent) {
    @NonDisplayProperty
    val parentElement: AbstractElement
        get() = parent as AbstractElement
}

abstract class Block(parent: Block?,
                     private val variableRegistry: VariableRegistry = VariableRegistryImpl(parent)
) : AbstractElement(parent), VariableRegistry by variableRegistry {
    @NonDisplayProperty
    val parentBlock: Block?
        get() = parent as Block?
}

abstract class SectionBlock(parent: Section?) : Block(parent) {
    @NonDisplayProperty
    val parentSection: Section?
        get() = parent as Section?
}

class MarkdownConstants {
    companion object {
        const val TitleProperty = "title"
        const val SubTitleProperty = "subTitle"

        const val DateVar = "date"
        const val TimeVar = "time"
        const val DateTimeVar = "datetime"
    }
}

/**
 * Document -> Section(Heading) -> Blocks -> Inline elements
 */
class MarkdownDocument(val paperRefDatabase: PaperRefDatabase,
                       parent: MarkdownDocument? = null,
                       private val refRegistry: RefRegistry = RefRegistryImpl()
) : Section(parent = parent, heading = null), RefRegistry by refRegistry {
    init {
        if (parent == null) {
            val now = Instant.now()
            updateVariable(MarkdownConstants.DateTimeVar,
                    DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒")
                            .withZone(ZoneId.systemDefault())
                            .format(now))
            updateVariable(MarkdownConstants.DateVar,
                    DateTimeFormatter.ofPattern("yyyy年MM月dd日")
                            .withZone(ZoneId.systemDefault())
                            .format(now))
            updateVariable(MarkdownConstants.TimeVar,
                    DateTimeFormatter.ofPattern("HH时mm分ss秒")
                            .withZone(ZoneId.systemDefault())
                            .format(now))
        }
    }

    private val paperRefList: MutableList<String> = mutableListOf()

    fun resolvePaperRefIndex(refId: String): Int {
        val index = paperRefList.indexOf(refId)
        return if (index == -1) {
            paperRefList.add(refId)
            paperRefList.size - 1
        } else {
            index
        }
    }

    fun loadRefEntries(): List<PaperRefEntry> {
        return paperRefList.map { paperRefDatabase.query(it) }
    }
}

open class Section(parent: Section?, var heading: Heading? = null) : SectionBlock(parent = parent), RefTarget {
    private val elementCounter: MutableMap<Class<Any>, Int> = mutableMapOf()

    val subSections: MutableList<Section> = mutableListOf()

    init {
        parent?.subSections?.add(this)
        parent?.children?.add(this)
    }

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    override fun attributesString(): String {
        return "[${fullSeqString()}] ${heading?.title()}"
    }

    val level: Int
        get() = heading?.level ?: 0

    val seq: Int
        get() = heading?.seq ?: 0

    fun fullSeqString(firstLevelChinese: Boolean = true): String {
        if (firstLevelChinese && (this.parentSection == null || this.parentSection?.seq == 0) && this.seq > 0) {
            return Convert.numberToChinese(this.seq.toDouble(), false) + "、"
        }

        var fullSeq = "$seq"
        var section = this
        while (section.parentSection != null && section.parentSection?.seq != 0) {
            fullSeq = "${section.parentSection?.seq}.$fullSeq"
            section = section.parentSection!!
        }

        return fullSeq
    }

    fun genCount(cls: Class<Any>): Int {
        return if (elementCounter.containsKey(cls)) {
            val value: Int = elementCounter.getValue(cls)
            elementCounter[cls] = value + 1
            value
        } else {
            val value = 1
            elementCounter[cls] = value + 1
            value
        }
    }

    override var refId: String = "sec-" + fullSeqString(false)
    override var refLabel: String = "章节"
    override val title: String
        get() = heading?.title() ?: ""
    override val fixSeq: String?
        get() = fullSeqString(false)
}

class AbstractSection(parent: MarkdownDocument, val isChinese: Boolean) : Section(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class RefSection(parent: MarkdownDocument) : Section(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class Heading(parent: Section, val level: Int, val seq: Int, title: String = "") : SectionBlock(parent) {
    init {
        appendChild(Text(this, title))
    }

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    fun title(title: String): Heading {
        children[0] = Text(parent = this, content = title)
        return this
    }

    fun title(): String {
        return (children[0] as Text).content
    }

    fun fullSeqString(): String {
        return (parent as Section).fullSeqString()
    }
}

class Paragraph(parent: Block) : Block(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

abstract class AbstractListBlock(parent: Block, val indent: Int, val level: Int) : Block(parent = parent) {
    var currentIndex = 0
}

class OrderedList(parent: Block, indent: Int, level: Int, val startNumber: Int = 1) :
        AbstractListBlock(parent = parent, indent = indent, level = level) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class BulletList(parent: Block, indent: Int, level: Int, val marker: String) :
        AbstractListBlock(parent = parent, indent = indent, level = level) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class ListItem(parent: AbstractListBlock, val index: Int) : Block(parent = parent) {
    val parentList: AbstractListBlock
        get() = parent as AbstractListBlock

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class IndentCodeBlock(parent: Section, val content: String) : Block(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class FencedCodeBlock(parent: Section, val lang: String, val content: String, val indent: Int,
                      override val title: String = "", override val refId: String = "")
    : SectionBlock(parent = parent), RefTarget {
    companion object {
        const val DEFAULT_LANG = ""
    }

    override val refLabel: String = "代码"

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    override val fixSeq: String?
        get() = null
}

class ImageBlock(parent: Section, val url: String, override val title: String, override val refId: String)
    : SectionBlock(parent = parent), RefTarget {
    override val refLabel: String = "图"

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    override val fixSeq: String?
        get() = null
}

class BlockQuote(parent: Block, val source: String?) : Block(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class ThematicBreak(parent: Block) : Block(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class KeyWordBlock(parent: Section, val keywords: List<String>) : SectionBlock(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

enum class HorizonAlign {
    LEFT,
    RIGHT,
    MIDDLE,
    BOTH
}

class TableCell(parent: TableRow, val horizonAlign: HorizonAlign = HorizonAlign.BOTH, val indent: Int = 0)
    : Block(parent = parent) {
    @NonDisplayProperty
    private var internalDocument: MarkdownDocument? = null

    @NonDisplayProperty
    val nestDocument: MarkdownDocument
        get() {
            if (internalDocument == null) {
                internalDocument = MarkdownDocument(document!!.paperRefDatabase, document)
            }

            return internalDocument!!
        }

    init {
        parent.appendChild(this)
        parent.cells.add(this)
    }

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    fun moveChildren() {
        if (internalDocument == null) {
            return
        }

        val document = internalDocument!!
        for (child in document.children.toList()) {
            appendChild(child)
        }
    }
}

class TableRow(parent: TableBlock, val isHeader: Boolean) : Block(parent = parent) {
    @NonDisplayProperty
    val cells: MutableList<TableCell> = mutableListOf()

    init {
        parent.appendChild(this)
        parent.rows.add(this)
    }

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class TableBlock(parent: Section, override var title: String, override var refId: String)
    : SectionBlock(parent = parent), RefTarget {
    @NonDisplayProperty
    val rows: MutableList<TableRow> = mutableListOf()
    override val refLabel: String = "表"

    val columnCount: Int
        get() {
            return if (rows.isEmpty()) {
                0
            } else {
                rows.map { it.cells.size }.maxOrNull()!!
            }
        }

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    override val fixSeq: String?
        get() = null
}

class TocBlock(parent: MarkdownDocument) : Section(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

abstract class AlgorithmMeta(parent: AlgorithmBlock, val name: String) : Block(parent = parent)

class AlgorithmInput(parent: AlgorithmBlock, name: String) : AlgorithmMeta(parent = parent, name = name) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class AlgorithmOutput(parent: AlgorithmBlock, name: String) : AlgorithmMeta(parent = parent, name = name) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class AlgorithmLine(parent: AlgorithmBlock) : Block(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class AlgorithmBlock(parent: Section, override val title: String, override val refId: String)
    : SectionBlock(parent = parent), RefTarget {
    override val refLabel: String = "算法"

    @NonDisplayProperty
    val lines: MutableList<AlgorithmLine> = mutableListOf()

    @NonDisplayProperty
    val metaList: MutableList<AlgorithmMeta> = mutableListOf()

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    override val fixSeq: String?
        get() = null
}

class FormulaBlock(parent: Section, val formula: Formula, override val title: String, override val refId: String)
    : SectionBlock(parent = parent), RefTarget {
    override val refLabel: String = "公式"

    init {
        formula.parent = this
    }

    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }

    override val fixSeq: String?
        get() = null
}

class HardLineBreak(parent: AbstractElement) : Inline(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

open class Text(parent: AbstractElement, override var content: String) : Inline(parent = parent), TextContent {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class Image(parent: AbstractElement, val url: String, val title: String) : Inline(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class Code(parent: AbstractElement, override val content: String) : Inline(parent = parent), TextContent {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class Emphasis(parent: AbstractElement, override val content: String) : Inline(parent = parent), TextContent {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class ItalyText(parent: AbstractElement, override val content: String) : Inline(parent = parent), TextContent {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class DeleteText(parent: AbstractElement, override val content: String) : Inline(parent = parent), TextContent {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class UrlLink(parent: AbstractElement, val title: String, val url: String) : Inline(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class RefLink(parent: AbstractElement, val title: String, val ref: String) : Inline(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class Formula(parent: AbstractElement?, override val content: String) : Inline(parent = parent), TextContent {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class EntityText(parent: AbstractElement, content: String, val entity: String) : Text(parent, content) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class Footnote(parent: AbstractElement) : Inline(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}

class PaperRef(parent: AbstractElement, val refId: String) : Inline(parent = parent) {
    override fun accept(visitor: Visitor) {
        visitor.visit(this)
    }
}